const Buffer = require('./buffer');
const authsettings = require('./settings');
//const { scope: seaIndexedDb } = require('./indexed')
const queryGunAliases = require('./query');
const parseProps = require('./parse');
const updateStorage = require('./update');
const SEA = require('./sea');
const Gun = SEA.Gun;
const finalizeLogin = require('./login');

// This internal func recalls persisted User authentication if so configured
const authRecall = async (gunRoot, authprops) => {
  // window.sessionStorage only holds signed { alias, pin } !!!
  const remember = authprops || sessionStorage.getItem('remember');
  const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {}; // @mhelander what is pIn?
  const pin = pIn && Buffer.from(pIn, 'utf8').toString('base64');
  // Checks for existing proof, matching alias and expiration:
  const checkRememberData = async ({
    proof,
    alias: aLias,
    iat,
    exp,
    remember
  }) => {
    if (!!proof && alias === aLias) {
      const checkNotExpired = args => {
        if (Math.floor(Date.now() / 1000) < iat + args.exp) {
          // No way hook to update 'iat'
          return Object.assign(args, { iat: iat, proof: proof });
        } else {
          Gun.log('Authentication expired!');
        }
      };
      // We're not gonna give proof to hook!
      const hooked = authsettings.hook({
        alias: alias,
        iat: iat,
        exp: exp,
        remember: remember
      });
      return (
        (hooked instanceof Promise && (await hooked.then(checkNotExpired))) ||
        checkNotExpired(hooked)
      );
    }
  };
  const readAndDecrypt = async (data, pub, key) =>
    parseProps(await SEA.decrypt(await SEA.verify(data, pub), key));

  // Already authenticated?
  if (
    gunRoot._.user &&
    Gun.obj.has(gunRoot._.user._, 'pub') &&
    Gun.obj.has(gunRoot._.user._, 'sea')
  ) {
    return gunRoot._.user._; // Yes, we're done here.
  }
  // No, got persisted 'alias'?
  if (!alias) {
    throw { err: 'No authentication session found!' };
  }
  // Yes, got persisted 'remember'?
  if (!remember) {
    throw {
      // And return proof if for matching alias
      err:
        ((await seaIndexedDb.get(alias, 'auth')) &&
          authsettings.validity &&
          'Missing PIN and alias!') ||
        'No authentication session found!'
    };
  }
  // Yes, let's get (all?) matching aliases
  const aliases = (await queryGunAliases(alias, gunRoot)).filter(
    ({ pub } = {}) => !!pub
  );
  // Got any?
  if (!aliases.length) {
    throw { err: 'Public key does not exist!' };
  }
  let err;
  // Yes, then attempt to log into each one until we find ours!
  // (if two users have the same username AND the same password... that would be bad)
  const [{ key, at, proof, pin: newPin } = {}] = await Promise.all(
    aliases
      .filter(({ at: { put } = {} }) => !!put)
      .map(async ({ at: at, pub: pub }) => {
        const readStorageData = async args => {
          const props =
            args || parseProps(await SEA.verify(remember, pub, true));
          let pin = props.pin;
          let aLias = props.alias;

          const data =
            !pin && alias === aLias
              ? // No PIN, let's try short-term proof if for matching alias
              await checkRememberData(props)
              : // Got PIN so get IndexedDB secret if signature is ok
              await checkRememberData(
                await readAndDecrypt(
                  await seaIndexedDb.get(alias, 'auth'),
                  pub,
                  pin
                )
              );
          pin = pin || data.pin;
          delete data.pin;
          return { pin: pin, data: data };
        };
        // got pub, try auth with pin & alias :: or unwrap Storage data...
        const __gky20 = await readStorageData(pin && { pin, alias });
        const data = __gky20.data;
        const newPin = __gky20.pin;
        const proof = data.proof;

        if (!proof) {
          if (!data) {
            err = 'No valid authentication session found!';
            return;
          }
          try {
            // Wipes IndexedDB silently
            await updateStorage()(data);
          } catch (e) {} //eslint-disable-line no-empty
          err = 'Expired session!';
          return;
        }

        try {
          // auth parsing or decryption fails or returns empty - silently done
          const auth = at.put.auth.auth;
          const sea = await SEA.decrypt(auth, proof);
          if (!sea) {
            err = 'Failed to decrypt private key!';
            return;
          }
          const priv = sea.priv;
          const epriv = sea.epriv;
          const epub = at.put.epub;
          // Success! we've found our private data!
          err = null;
          return {
            proof: proof,
            at: at,
            pin: newPin,
            key: { pub: pub, priv: priv, epriv: epriv, epub: epub }
          };
        } catch (e) {
          err = 'Failed to decrypt private key!';
          return;
        }
      })
      .filter(props => !!props)
  );

  if (!key) {
    throw { err: err || 'Public key does not exist!' };
  }

  // now we have AES decrypted the private key,
  // if we were successful, then that means we're logged in!
  try {
    await updateStorage(proof, key, newPin || pin)(key);

    const user = Object.assign(key, { at: at, proof: proof });
    const pIN = newPin || pin;

    const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') };

    return await finalizeLogin(alias, user, gunRoot, pinProp);
  } catch (e) {
    // TODO: right log message ?
    Gun.log('Failed to finalize login with new password!');
    const { err = '' } = e || {};
    throw { err: 'Finalizing new password login failed! Reason: ' + err };
  }
};
module.exports = authRecall;
